#pragma once

#include <cstdint>
#include <list>
#include <ctime>
#include <string>
#include <stdexcept>
#include "../util/allocator.hh"
#include "../logger/writer.hh"
#include "../timer_queue.hh"
#include "../object_pool.hpp"

#ifdef KRATOS_COMPILER_LOG_OFF

#define EVENT_QUEUE_LOG_ERROR(a, b)

#define EVENT_QUEUE_LOG_EXCEPTION(e, a, b)

#define EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(a)\
    return false;

#define EVENT_QUEUE_PARAM_FAIL_RETURN(a, b)\
    return b;

#else

#define EVENT_QUEUE_LOG_ERROR(a, b)\
    logwcom("EventQueue") << a << b << logend;

#define EVENT_QUEUE_LOG_EXCEPTION(e, a, b)\
    logwcom("EventQueue") << "Exception:" << e.what() <<","<< a << b << logend;

#define EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(a)\
    if (a) {\
        logwcom("EventQueue") << "[" << __FILE__ << ":" << __LINE__ << "]:" << #a << ", check failed" << logend;\
        return false;\
    }

#define EVENT_QUEUE_PARAM_FAIL_RETURN(a, b)\
    if (a) {\
        logwcom("EventQueue") << "[" << __FILE__ << ":" << __LINE__ << "]:" << #a << ", check failed" << logend;\
        return b;\
    }

#endif // KRATOS_COMPILER_LOG_OFF

namespace kratos { namespace corelib {

// Event object wrapper for user-defined object in object pool
template <typename T>
class DiposeWrapper : public T {
public:
    template <typename...Args>
    DiposeWrapper(Args...args)
        : T(args...) {
    }
    virtual ~DiposeWrapper() {}
    virtual void dispose() {
        kratos::corelib::dispose(this);
    }
};

class EventListener;
class EventObject;
using EventID = std::uint32_t; // Event ID type

using ListenerList = std::list<EventListener*>; // EventListener list

// Event Queue, single thread ONLY
class EventQueue {
public:
    // ctor
    EventQueue();
    // dtor
    ~EventQueue();
    // run AYSNC event and processes event timer
    void update();
    // registers event handler
    // @param eventID eventID, only listener is registerred if ZERO
    // @param listener event listener pointer
    // @retval true
    // @retval false
    bool addListener(EventID eventID, EventListener* listener);
    // Un-registers event handler
    // @param eventID eventID, removing listener only if ZERO
    // @param listener event listener pointer
    // @retval true
    // @retval false
    bool removeListener(EventID eventID, EventListener* listener);
    // Un-register all event handlers for eventID
    // @param eventID event ID
    // @retval true
    // @retval false
    bool removeAllListener(EventID eventID);
    // query event listener for eventID
    // @param eventID event ID
    // @return event listeners for this eventID
    const ListenerList& getListeners(EventID eventID);
    // query event listener for receiver
    // @param receiver receiver ID
    // @return event listener
    EventListener* getListener(std::uint64_t receiver);
    // publish a event and do it now
    // @param args the argument of ctor of T
    // @retval true
    // @retval false
    template <typename T, typename...Args>
    bool fireEventNow(Args...args) {
        auto eo = allocate<DiposeWrapper<T>>(args...);
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo);
        auto result = fireEvent(*eo);
        eo->dispose();
        return result;
    }
    // publish a event and do it now
    // @param evt user defined event
    // @retval true
    // @retval false
    template <typename T>
    bool fireEventNow(T* evt) {
        DiposeWrapper<T>* eo = nullptr;
        try {
            eo = dynamic_cast<DiposeWrapper<T>>(evt);
        } catch (std::exception& e) {
            EVENT_QUEUE_LOG_EXCEPTION(e, "dynamic_cast to ", typeid(T).name());
        }
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo);
        auto result = fireEvent(*evt);
        eo->dispose();
        return result;
    }
    // publish event and do it later
    // @param timeout timer's timeout in millionseconds for event
    // @param args the argument of ctor of T
    // @retval true
    // @retval false
    template <typename T, typename...Args>
    bool fireEventLater(std::time_t timeout, Args...args) {
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!timeout);
        auto eo = allocate<DiposeWrapper<T>>(args...);
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo);
        return fireEvent(eo, timeout);
    }
    // publish event and do it later
    // @param evt user defined event
    // @param timeout timer's timeout in millionseconds for event
    // @retval true
    // @retval false
    template <typename T>
    bool fireEventLater(T* evt, std::time_t timeout) {
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!timeout);
        return fireEvent(evt, timeout);
    }
    // publish event and do it later
    // @param timeout timer's timeout in millionseconds for event
    // @param args the argument of ctor of T
    // @retval true
    // @retval false
    template <typename T, typename...Args>
    bool fireEventPeriod(std::time_t timeout, Args...args) {
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!timeout);
        auto eo = allocate<DiposeWrapper<T>>(args...);
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!eo);
        eo->setPeriod();
        return fireEvent(eo, timeout);
    }
    // publish event and do it later
    // @param evt user defined event
    // @param timeout timer's timeout in millionseconds for event
    // @retval true
    // @retval false
    template <typename T>
    bool fireEventPeriod(T* evt, std::time_t timeout) {
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!timeout);
        EVENT_QUEUE_PARAM_FAIL_RETURN_FALSE(!evt);
        evt->setPeriod();
        return fireEvent(evt, timeout);
    }
    // create a user defined event
    // @param args the argument of ctor of T
    // @return pointer of type T
    template <typename T, typename...Args>
    T* createEvent(Args...args) {
        auto eo = allocate<DiposeWrapper<T>>(args...);
        EVENT_QUEUE_PARAM_FAIL_RETURN(!eo, nullptr);
        return dynamic_cast<T*>(eo);
    }
    // query EventQueue, creates a new one for first query
    // @param name EventQueue's name
    // @return EventQueue
    static EventQueue* getEventQueue(const std::string& name);
    // runs EventQueue::update()
    static void updateEventQueue();

private:
    // publish a event and do it now
    // @param eo event object
    // @retval true
    // @retval false
    bool fireEvent(EventObject& eo);
    // publish event and do it later
    // @param eo event object
    // @param timeout timer's timeout for event
    // @retval true
    // @retval false
    bool fireEvent(EventObject* eo, std::time_t timeout);

private:
    using EventMap = UNORDERED_MAP(EventID, ListenerList, EventQueue_eventHandlerMap_);
    using TimerEventMap = UNORDERED_MAP(TimerID, EventObject*, EventQueue_timerEventMap_);
    using ListenerMap = UNORDERED_MAP(std::uint64_t, EventListener*, EventQueue_listenerMap_);
    ListenerMap listenerMap_; // {Listener ID(receiver), EventListener*}
    EventMap eventHandlerMap_; // {EventID, EventListener*}
    TimerQueue timerQueue_; // TimerQueue
    TimerEventMap timerEventMap_; // {TimerID, EventObject*}, deferred event timer
};

}}

